// Copyright 2021 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "src/dawn_node/binding/GPURenderBundleEncoder.h"

#include "src/dawn_node/binding/Converter.h"
#include "src/dawn_node/binding/GPUBindGroup.h"
#include "src/dawn_node/binding/GPUBuffer.h"
#include "src/dawn_node/binding/GPURenderBundle.h"
#include "src/dawn_node/binding/GPURenderPipeline.h"
#include "src/dawn_node/utils/Debug.h"

namespace wgpu::binding {

    ////////////////////////////////////////////////////////////////////////////////
    // wgpu::bindings::GPURenderBundleEncoder
    ////////////////////////////////////////////////////////////////////////////////
    GPURenderBundleEncoder::GPURenderBundleEncoder(wgpu::RenderBundleEncoder enc)
        : enc_(std::move(enc)) {
    }

    interop::Interface<interop::GPURenderBundle> GPURenderBundleEncoder::finish(
        Napi::Env env,
        interop::GPURenderBundleDescriptor descriptor) {
        wgpu::RenderBundleDescriptor desc{};

        return interop::GPURenderBundle::Create<GPURenderBundle>(env, enc_.Finish(&desc));
    }

    void GPURenderBundleEncoder::setBindGroup(
        Napi::Env env,
        interop::GPUIndex32 index,
        interop::Interface<interop::GPUBindGroup> bindGroup,
        std::vector<interop::GPUBufferDynamicOffset> dynamicOffsets) {
        Converter conv(env);

        wgpu::BindGroup bg{};
        uint32_t* offsets = nullptr;
        uint32_t num_offsets = 0;
        if (!conv(bg, bindGroup) || !conv(offsets, num_offsets, dynamicOffsets)) {
            return;
        }

        enc_.SetBindGroup(index, bg, num_offsets, offsets);
    }

    void GPURenderBundleEncoder::setBindGroup(Napi::Env env,
                                              interop::GPUIndex32 index,
                                              interop::Interface<interop::GPUBindGroup> bindGroup,
                                              interop::Uint32Array dynamicOffsetsData,
                                              interop::GPUSize64 dynamicOffsetsDataStart,
                                              interop::GPUSize32 dynamicOffsetsDataLength) {
        Converter conv(env);

        wgpu::BindGroup bg{};
        if (!conv(bg, bindGroup)) {
            return;
        }

        enc_.SetBindGroup(index, bg, dynamicOffsetsDataLength,
                          dynamicOffsetsData.Data() + dynamicOffsetsDataStart);
    }

    void GPURenderBundleEncoder::pushDebugGroup(Napi::Env, std::string groupLabel) {
        enc_.PushDebugGroup(groupLabel.c_str());
    }

    void GPURenderBundleEncoder::popDebugGroup(Napi::Env) {
        enc_.PopDebugGroup();
    }

    void GPURenderBundleEncoder::insertDebugMarker(Napi::Env, std::string markerLabel) {
        enc_.InsertDebugMarker(markerLabel.c_str());
    }

    void GPURenderBundleEncoder::setPipeline(
        Napi::Env env,
        interop::Interface<interop::GPURenderPipeline> pipeline) {
        Converter conv(env);

        wgpu::RenderPipeline p{};
        if (!conv(p, pipeline)) {
            return;
        }

        enc_.SetPipeline(p);
    }

    void GPURenderBundleEncoder::setIndexBuffer(Napi::Env env,
                                                interop::Interface<interop::GPUBuffer> buffer,
                                                interop::GPUIndexFormat indexFormat,
                                                interop::GPUSize64 offset,
                                                std::optional<interop::GPUSize64> size) {
        Converter conv(env);

        wgpu::Buffer b{};
        wgpu::IndexFormat f{};
        uint64_t o = 0;
        uint64_t s = wgpu::kWholeSize;
        if (!conv(b, buffer) ||       //
            !conv(f, indexFormat) ||  //
            !conv(o, offset) ||       //
            !conv(s, size)) {
            return;
        }

        enc_.SetIndexBuffer(b, f, o, s);
    }

    void GPURenderBundleEncoder::setVertexBuffer(Napi::Env env,
                                                 interop::GPUIndex32 slot,
                                                 interop::Interface<interop::GPUBuffer> buffer,
                                                 interop::GPUSize64 offset,
                                                 std::optional<interop::GPUSize64> size) {
        Converter conv(env);

        wgpu::Buffer b{};
        uint64_t s = wgpu::kWholeSize;
        if (!conv(b, buffer) || !conv(s, size)) {
            return;
        }
        enc_.SetVertexBuffer(slot, b, offset, s);
    }

    void GPURenderBundleEncoder::draw(Napi::Env env,
                                      interop::GPUSize32 vertexCount,
                                      interop::GPUSize32 instanceCount,
                                      interop::GPUSize32 firstVertex,
                                      interop::GPUSize32 firstInstance) {
        enc_.Draw(vertexCount, instanceCount, firstVertex, firstInstance);
    }

    void GPURenderBundleEncoder::drawIndexed(Napi::Env env,
                                             interop::GPUSize32 indexCount,
                                             interop::GPUSize32 instanceCount,
                                             interop::GPUSize32 firstIndex,
                                             interop::GPUSignedOffset32 baseVertex,
                                             interop::GPUSize32 firstInstance) {
        enc_.DrawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance);
    }

    void GPURenderBundleEncoder::drawIndirect(Napi::Env env,
                                              interop::Interface<interop::GPUBuffer> indirectBuffer,
                                              interop::GPUSize64 indirectOffset) {
        Converter conv(env);

        wgpu::Buffer b{};
        uint32_t o = 0;

        if (!conv(b, indirectBuffer) ||  //
            !conv(o, indirectOffset)) {
            return;
        }
        enc_.DrawIndirect(b, o);
    }

    void GPURenderBundleEncoder::drawIndexedIndirect(
        Napi::Env env,
        interop::Interface<interop::GPUBuffer> indirectBuffer,
        interop::GPUSize64 indirectOffset) {
        Converter conv(env);

        wgpu::Buffer b{};
        uint32_t o = 0;

        if (!conv(b, indirectBuffer) ||  //
            !conv(o, indirectOffset)) {
            return;
        }
        enc_.DrawIndexedIndirect(b, o);
    }

    std::optional<std::string> GPURenderBundleEncoder::getLabel(Napi::Env) {
        UNIMPLEMENTED();
    }

    void GPURenderBundleEncoder::setLabel(Napi::Env, std::optional<std::string> value) {
        UNIMPLEMENTED();
    }

}  // namespace wgpu::binding
